#include "OBJModelLoader.h"
#include "Material.h"
#include "Face.h"
#include <iostream>
#include <fstream>
#include <algorithm>
#include "ModelUtil.h"
#include "MeshBuffer.h"

#pragma warning(disable:4996)

namespace steam
{
	namespace coal
	{
		IMesh* OBJModelLoader::load( std::string filePath )
		{
			std::string filePathStr(filePath);
			std::string dirPath;
			if (filePathStr.find_last_of('\\') != std::string::npos)
			{
				dirPath = filePathStr.substr(0, filePathStr.find_last_of('\\')+1);
			}

			std::ifstream f(filePath.c_str());
			if (!f.is_open())
			{
				std::cout << "File open failed : " << filePath << "\n";
				return NULL;
			}

			std::vector<std::string> lines;
			char buffer[256];
			while (!f.eof()) 
			{
				f.getline(buffer, 256);
				lines.push_back(std::string(buffer));
			}

			f.close();

			Mesh* mesh = new Mesh;
			MeshBuffer* meshBuffer = NULL;
			extractMaterials(lines, mesh, dirPath);

			std::vector<std::string>::iterator it = lines.begin();

			for (; it != lines.end(); ++it)
			{
				std::string s = *it;

				if (s.empty()) continue;
				if (startsWith("# ", s) == true) continue;

				if (startsWith("v ", s) == true)
				{
					Vertex vertex;
					sscanf(s.c_str(), "v %f %f %f", &vertex.x, &vertex.y, &vertex.z);
					mesh->addVertex(vertex);
				}
				else if (startsWith("vt ", s) == true)
				{
					TexCoord texCoord;
					sscanf(s.c_str(), "vt %f %f", &texCoord.s, &texCoord.t);
					mesh->addTexCoord(texCoord);
				}
				else if (startsWith("vn ", s) == true)
				{
					Normal normal;
					sscanf(s.c_str(), "vn %f %f %f", &normal.x, &normal.y, &normal.z);
					mesh->addNormal(normal);
				}
				else if (startsWith("f ", s) == true)
				{
					FaceDataType mType = getMeshType(s);

					switch (mType)
					{
					case FACE_NOT_SUPPORTED : 
						{
							std::cout << "Unsupported mesh. Ignoring...\n";
							break;
						}
					case TRIANGLE_WITH_NORM_NO_UV:
						{
							Face* face = new Face;
							int v1, v2, v3, n1, n2, n3;

							sscanf(s.c_str(), "f %d//%d %d//%d %d//%d", &v1, &n1, &v2, &n2, &v3, &n3);
							v1--; v2--; v3--; n1--; n2--; n3--;

							ModelUtil::addVertex(face, v1, v2, v3);
							ModelUtil::addNormal(face, n1, n2, n3);
							meshBuffer->addFace(face);
							break;
						}
					case TRIANGLE_WITH_NO_UV_NO_NORM:
						{
							Face* face = new Face;
							int v1, v2, v3;
							sscanf(s.c_str(), "f %d %d %d", &v1, &v2, &v3);
							v1--; v2--; v3--;

							ModelUtil::addVertex(face, v1, v2, v3);
							meshBuffer->addFace(face);
							break;
						}
					case TRIANGLE_WITH_UV_NORM:
						{
							Face* face = new Face;
							int v1, v2, v3, n1, n2, n3, t1, t2, t3;
							sscanf(s.c_str(), "f %d/%d/%d %d/%d/%d %d/%d/%d", 
								&v1, &t1, &n1,
								&v2, &t2, &n2,
								&v3, &t3, &n3
								);
							v1--; v2--; v3--; n1--; n2--; n3--; t1--; t2--; t3--;

							ModelUtil::addVertex(face, v1, v2, v3);
							ModelUtil::addNormal(face, n1, n2, n3);
							ModelUtil::addTexCoord(face, t1, t2, t3);
							meshBuffer->addFace(face);
							break;
						}
					case TRIANGLE_WITH_UV_NO_NORM:
						{
							Face* face = new Face;
							int v1, v2, v3, t1, t2, t3;
							sscanf(s.c_str(), "f %d/%d %d/%d %d/%d", &v1, &t1, &v2, &t2, &v3, &t3);
							v1--; v2--; v3--; t1--; t2--; t3--;

							ModelUtil::addVertex(face, v1, v2, v3);
							ModelUtil::addTexCoord(face, t1, t2, t3);
							meshBuffer->addFace(face);
							break;
						}
					case QUAD_WITH_NORM_NO_UV:
					case QUAD_WITH_NO_UV_NO_NORM:
					case QUAD_WITH_UV_NORM:
					case QUAD_WITH_UV_NO_NORM:
						{
							std::cout << "QUAD faces not implemented yet\n";
							break;
						}
					default:
						break;
					};
				}
				else if (startsWith("usemtl ", s) == true)
				{
					char mtlName[256];
					sscanf(s.c_str(), "usemtl %s", mtlName);

					if (meshBuffer != NULL)
					{
						mesh->addMeshBuffer(meshBuffer);
					}
					std::string mtl(mtlName);
					meshBuffer = new MeshBuffer;
					meshBuffer->setMaterialName(mtl);
				}
			}
			if (meshBuffer != NULL)
			{
				mesh->addMeshBuffer(meshBuffer);
			}
			return mesh;
		}

		void OBJModelLoader::extractMaterials(std::vector<std::string>& lines, Mesh* mesh, std::string dirPath)
		{
			std::vector<std::string>::iterator it = lines.begin();
			for (; it != lines.end(); ++it)
			{
				if ((*it).empty()) 
					continue;
				if (startsWith("mtllib", *it) == true)
				{
					char mtlFileName[256];
					sscanf((*it).c_str(), "mtllib %s", mtlFileName);
					processMaterials(mesh, mtlFileName, dirPath);
				}
			}
		}

		void OBJModelLoader::processMaterials(Mesh* mesh, std::string fileName, std::string dirPath)
		{
			std::string fullPath = dirPath + fileName;
			std::ifstream f(fullPath.c_str());
			if (!f.is_open())
			{
				std::cout << "File open failed : " << fullPath << "\n";
				return;
			}

			std::vector<std::string> lines;
			char buffer[256];
			while (!f.eof()) 
			{
				f.getline(buffer, 256);
				lines.push_back(std::string(buffer));
			}

			f.close();
			Material* material = NULL;

			for (unsigned int i=0; i<lines.size(); ++i)
			{
				std::string line = lines.at(i);
				if (startsWith("newmtl", line) == true)
				{
					sscanf(line.c_str(), "newmtl %s", buffer);
					if (material != NULL)
					{
						mesh->addMaterial(material);
					}
					material = new Material;
					material->setMaterialName(std::string(buffer));
				}
				else if (startsWith("Ka", line) == true)
				{
					Color3f color;
					sscanf(line.c_str(), "Ka %f %f %f", &color.r, &color.g, &color.b);
					if (material != NULL)
					{
						material->setAmbientColor(color);
					}
				}
				else if (startsWith("Kd", line) == true)
				{
					Color3f color;
					sscanf(line.c_str(), "Kd %f %f %f", &color.r, &color.g, &color.b);
					if (material != NULL)
					{
						material->setDiffuseColor(color);
					}
				}
				else if (startsWith("Ks", line) == true)
				{
					Color3f color;
					sscanf(line.c_str(), "Ks %f %f %f", &color.r, &color.g, &color.b);
					if (material != NULL)
					{
						material->setSpecularColor(color);
					}
				}
				else if (startsWith("map_Ka", line) == true)
				{
					sscanf(line.c_str(), "map_Kd %s", buffer);
					if (material != NULL)
					{
						material->setSpecularTexture(dirPath + std::string(buffer));
					}
				}
				else if (startsWith("map_Kd", line) == true)
				{
					sscanf(line.c_str(), "map_Kd %s", buffer);
					if (material != NULL)
					{
						material->setDiffuseTexture(dirPath + std::string(buffer));
					}
				}
				else if (startsWith("map_Ks", line) == true)
				{
					sscanf(line.c_str(), "map_Kd %s", buffer);
					if (material != NULL)
					{
						material->setSpecularTexture(dirPath + std::string(buffer));
					}
				}
			}
			if (material != NULL)
			{
				mesh->addMaterial(material);
			}
		}

		bool OBJModelLoader::startsWith(std::string starter, std::string text)
		{
			if (starter.length() > text.length())
				return false;

			std::string::iterator starterIt = starter.begin();
			std::string::iterator textIt = text.begin();

			for (; starterIt != starter.end(); ++starterIt, ++textIt)
			{
				if (*starterIt != *textIt)
					return false;
			}
			return true;
		}

		OBJModelLoader::FaceDataType OBJModelLoader::getMeshType(std::string str)
		{
			FaceDataType mType = FACE_NOT_SUPPORTED;

			if (std::count(str.begin(), str.end(), '/') == 0)
			{
				if (std::count(str.begin(), str.end(), ' ') == 3)
				{
					mType = TRIANGLE_WITH_NO_UV_NO_NORM;
				}
				else if (std::count(str.begin(), str.end(), ' ') == 4)
				{
					mType = QUAD_WITH_NO_UV_NO_NORM;
				}
			}
			else
			{
				if (std::count(str.begin(), str.end(), '/') == 3)
				{
					mType = TRIANGLE_WITH_UV_NO_NORM;
				}
				else if (std::count(str.begin(), str.end(), '/') == 6)
				{
					mType = (str.find("//") == std::string::npos) ? TRIANGLE_WITH_UV_NORM : TRIANGLE_WITH_NORM_NO_UV;
				}
				else if (std::count(str.begin(), str.end(), '/') == 4)
				{
					mType = QUAD_WITH_UV_NO_NORM;
				}
				else if (std::count(str.begin(), str.end(), '/') == 8)
				{
					mType = (str.find("//") != std::string::npos) ? QUAD_WITH_UV_NORM : QUAD_WITH_NORM_NO_UV;
				}
			}

			return mType;
		}

	}

}
